<!DOCTYPE HTML>
<html lang="zh-Hans" class="sidebar-visible no-js light">
    <head>
        <!-- Book generated using mdBook -->
        <meta charset="UTF-8">
        <title>Rust web frameworks have subpar error reporting - Rust 优秀博文</title>


        <!-- Custom HTML head -->
        
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="theme-color" content="#ffffff" />

        <link rel="icon" href="../../favicon.svg">
        <link rel="shortcut icon" href="../../favicon.png">
        <link rel="stylesheet" href="../../css/variables.css">
        <link rel="stylesheet" href="../../css/general.css">
        <link rel="stylesheet" href="../../css/chrome.css">
        <link rel="stylesheet" href="../../css/print.css" media="print">

        <!-- Fonts -->
        <link rel="stylesheet" href="../../FontAwesome/css/font-awesome.css">
        <link rel="stylesheet" href="../../fonts/fonts.css">

        <!-- Highlight.js Stylesheets -->
        <link rel="stylesheet" href="../../highlight.css">
        <link rel="stylesheet" href="../../tomorrow-night.css">
        <link rel="stylesheet" href="../../ayu-highlight.css">

        <!-- Custom theme stylesheets -->

    </head>
    <body>
        <!-- Provide site root to javascript -->
        <script type="text/javascript">
            var path_to_root = "../../";
            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
        </script>

        <!-- Work around some values being stored in localStorage wrapped in quotes -->
        <script type="text/javascript">
            try {
                var theme = localStorage.getItem('mdbook-theme');
                var sidebar = localStorage.getItem('mdbook-sidebar');

                if (theme.startsWith('"') && theme.endsWith('"')) {
                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
                }

                if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
                }
            } catch (e) { }
        </script>

        <!-- Set the theme before any content is loaded, prevents flash -->
        <script type="text/javascript">
            var theme;
            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
            if (theme === null || theme === undefined) { theme = default_theme; }
            var html = document.querySelector('html');
            html.classList.remove('no-js')
            html.classList.remove('light')
            html.classList.add(theme);
            html.classList.add('js');
        </script>

        <!-- Hide / unhide sidebar before it is displayed -->
        <script type="text/javascript">
            var html = document.querySelector('html');
            var sidebar = 'hidden';
            if (document.body.clientWidth >= 1080) {
                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
                sidebar = sidebar || 'visible';
            }
            html.classList.remove('sidebar-visible');
            html.classList.add("sidebar-" + sidebar);
        </script>

        <nav id="sidebar" class="sidebar" aria-label="Table of contents">
            <div class="sidebar-scrollbox">
                <ol class="chapter"><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">1.</strong> 类型系统</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../01_类型系统/Rust_Concept_Clarification_Deref_vs_AsRef_vs_Borrow_vs_Cow/Rust_Concept_Clarification_Deref_vs_AsRef_vs_Borrow_vs_Cow.html"><strong aria-hidden="true">1.1.</strong> Rust Concept Clarification Deref vs AsRef vs Borrow vs Cow</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust_Concept_Clarification_Deref_vs_AsRef_vs_Borrow_vs_Cow/Deref_AsRef_Borrow_Cow释义.html"><strong aria-hidden="true">1.2.</strong> Deref AsRef Borrow Cow 释义</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust的Borrow和AsRef：让你的代码用起来像呼吸一样自然/Rust的Borrow和AsRef：让你的代码用起来像呼吸一样自然.html"><strong aria-hidden="true">1.3.</strong> Rust的Borrow和AsRef：让你的代码用起来像呼吸一样自然</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust的Cow类型有什么用？详解Cow及其用途/Rust的Cow类型有什么用？详解Cow及其用途.html"><strong aria-hidden="true">1.4.</strong> Rust的Cow类型有什么用？详解Cow及其用途</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/判别Fn、FnMut、FnOnce的标准/判别Fn、FnMut、FnOnce的标准.html"><strong aria-hidden="true">1.5.</strong> 判别Fn、FnMut、FnOnce的标准</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/一行代码告诉你内部可变性的真相(UnsafeCell)/一行代码告诉你内部可变性的真相(UnsafeCell).html"><strong aria-hidden="true">1.6.</strong> 一行代码告诉你内部可变性的真相(UnsafeCell)</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Tour_of_Rust's_Standard_Library_Traits/Tour_of_Rust's_Standard_Library_Traits.html"><strong aria-hidden="true">1.7.</strong> Tour of Rust's Standard Library Traits</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/逆变、协变与子类型，以及Rust/逆变、协变与子类型，以及Rust.html"><strong aria-hidden="true">1.8.</strong> 逆变、协变与子类型，以及Rust</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust自引用结构、Pin与Unpin/Rust自引用结构、Pin与Unpin.html"><strong aria-hidden="true">1.9.</strong> Rust自引用结构、Pin与Unpin</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/为什么Rust需要Pin,Unpin/为什么Rust需要Pin,Unpin.html"><strong aria-hidden="true">1.10.</strong> 译：为什么 Rust 需要 Pin, Unpin ？</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/定海神针Pin和Unpin/定海神针Pin和Unpin.html"><strong aria-hidden="true">1.11.</strong> 译：定海神针 Pin 和 Unpin</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/sizedness-in-rust/sizedness-in-rust.html"><strong aria-hidden="true">1.12.</strong> Sizedness in Rust</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/Rust生命周期集大成者PhantomData〈T〉/Rust生命周期集大成者PhantomData〈T〉.html"><strong aria-hidden="true">1.13.</strong> Rust生命周期集大成者 PhantomData&lt;T&gt;</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：用Rust做类型体操_Part_0.html"><strong aria-hidden="true">1.14.</strong> 数据库表达式执行的黑魔法：用Rust做类型体操 Part 0</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：GAT实现引用类型关联_Part_1.html"><strong aria-hidden="true">1.15.</strong> 数据库表达式执行的黑魔法：GAT实现引用类型关联 Part 1</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：用HRTB写bound_Part_2.html"><strong aria-hidden="true">1.16.</strong> 数据库表达式执行的黑魔法：用HRTB写bound Part 2</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：用Rust做类型体操之用宏展开重复代码_Part_3_&_4.html"><strong aria-hidden="true">1.17.</strong> 数据库表达式执行的黑魔法：用Rust做类型体操之用宏展开重复代码 Part 3 & 4</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：与Rust编译器斗智斗勇之表达式向量化_Part_5_&_6.html"><strong aria-hidden="true">1.18.</strong> 数据库表达式执行的黑魔法：与Rust编译器斗智斗勇之表达式向量化 Part 5 & 6</a></li><li class="chapter-item expanded "><a href="../../01_类型系统/数据库表达式执行的黑魔法：用Rust做类型体操/数据库表达式执行的黑魔法：在Rust中用宏关联逻辑类型和实际类型_Part_7.html"><strong aria-hidden="true">1.19.</strong> 数据库表达式执行的黑魔法：在Rust中用宏关联逻辑类型和实际类型 Part 7</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">2.</strong> 生命周期</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../02_生命周期/Rust中的生命周期——从StrSplit实例说开去/Rust中的生命周期——从StrSplit实例说开去.html"><strong aria-hidden="true">2.1.</strong> Rust中的生命周期——从StrSplit实例说开去</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/与ChatGPT深度对话来学Rust生命周期/与ChatGPT深度对话来学Rust生命周期.html"><strong aria-hidden="true">2.2.</strong> 与ChatGPT深度对话来学Rust生命周期</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——early_bound与late_bound（1）/进击的Rust生命周期——early_bound与late_bound（1）.html"><strong aria-hidden="true">2.3.</strong> 进击的Rust生命周期——early_bound与late_bound（1）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——early_bound与late_bound（2）/进击的Rust生命周期——early_bound与late_bound（2）.html"><strong aria-hidden="true">2.4.</strong> 进击的Rust生命周期——early_bound与late_bound（2）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——一力降十会的MIR（1）/进击的Rust生命周期——一力降十会的MIR（1）.html"><strong aria-hidden="true">2.5.</strong> 进击的Rust生命周期——一力降十会的MIR（1）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/进击的Rust生命周期——一力降十会的MIR（2）/进击的Rust生命周期——一力降十会的MIR（2）.html"><strong aria-hidden="true">2.6.</strong> 进击的Rust生命周期——一力降十会的MIR（2）</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/Common_Rust_Lifetime_Misconceptions/Common_Rust_Lifetime_Misconceptions.html"><strong aria-hidden="true">2.7.</strong> Common Rust Lifetime Misconceptions</a></li><li class="chapter-item expanded "><a href="../../02_生命周期/Rust生命周期常见误区/Rust生命周期常见误区.html"><strong aria-hidden="true">2.8.</strong> 译：Rust生命周期常见误区</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">3.</strong> 无畏并发</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../05_无畏并发/简单写个Rust无锁队列/简单写个Rust无锁队列.html"><strong aria-hidden="true">3.1.</strong> 简单写个Rust无锁队列</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/进击的Rust多线程——混合自旋锁/进击的Rust多线程——混合自旋锁.html"><strong aria-hidden="true">3.2.</strong> 进击的Rust多线程——混合自旋锁</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/An_unsafe_tour_of_Rust's_Send_and_Sync/An_unsafe_tour_of_Rust's_Send_and_Sync.html"><strong aria-hidden="true">3.3.</strong> An unsafe tour of Rust's Send and Sync</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/进击的Rust多线程——Send与Sync/进击的Rust多线程——Send与Sync.html"><strong aria-hidden="true">3.4.</strong> 进击的Rust多线程——Send与Sync</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/进击的Rust多线程——离经叛道的PhantomData/进击的Rust多线程——离经叛道的PhantomData.html"><strong aria-hidden="true">3.5.</strong> 进击的Rust多线程——离经叛道的PhantomData</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/Rust_Async_Pin概念解析/Rust_Async_Pin概念解析.html"><strong aria-hidden="true">3.6.</strong> Rust Async: Pin概念解析</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/Rust和C++的并发库对比/Rust和C++的并发库对比.html"><strong aria-hidden="true">3.7.</strong> 译：Rust 和 C++ 的并发库对比</a></li><li class="chapter-item expanded "><a href="../../05_无畏并发/Rust原子类型和内存排序/Rust原子类型和内存排序.html"><strong aria-hidden="true">3.8.</strong> Rust原子类型和内存排序</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">4.</strong> 网络编程</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../06_网络编程/从编解码层面理解WebSocket_手写一个WebSocket/从编解码层面理解WebSocket_手写一个WebSocket.html"><strong aria-hidden="true">4.1.</strong> 从编解码层面理解WebSocket 手写一 个WebSocket</a></li><li class="chapter-item expanded "><a href="../../06_网络编程/透过Rust探索系统的本原：网络篇/透过Rust探索系统的本原：网络篇.html"><strong aria-hidden="true">4.2.</strong> 透过Rust探索系统的本原：网络篇</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">5.</strong> 轮子系列</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../07_轮子系列/700行Rust写一个内存分配器/700行Rust写一个内存分配器.html"><strong aria-hidden="true">5.1.</strong> 700行Rust写一个内存分配器</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/Rust：网络库的实现思路/Rust：网络库的实现思路.html"><strong aria-hidden="true">5.2.</strong> Rust：网络库的实现思路</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/Rust异步运行时基础部件/Rust异步运行时基础部件.html"><strong aria-hidden="true">5.3.</strong> Rust异步运行时基础部件</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/使用Rust+epoll编写异步IO框架/使用Rust+epoll编写异步IO框架（1）.html"><strong aria-hidden="true">5.4.</strong> 使用Rust+epoll编写异步IO框架（1）</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/使用Rust+epoll编写异步IO框架/使用Rust+epoll编写异步IO框架（2）.html"><strong aria-hidden="true">5.5.</strong> 使用Rust+epoll编写异步IO框架（2）</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/使用Rust+epoll编写异步IO框架/使用Rust+epoll编写异步IO框架（3）.html"><strong aria-hidden="true">5.6.</strong> 使用Rust+epoll编写异步IO框架（3）</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day1.html"><strong aria-hidden="true">5.7.</strong> 用rust从零开发一套web框架：day1</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day2.html"><strong aria-hidden="true">5.8.</strong> 用rust从零开发一套web框架：day2</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day3.html"><strong aria-hidden="true">5.9.</strong> 用rust从零开发一套web框架：day3</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day4.html"><strong aria-hidden="true">5.10.</strong> 用rust从零开发一套web框架：day4</a></li><li class="chapter-item expanded "><a href="../../07_轮子系列/用rust从零开发一套web框架/用rust从零开发一套web框架：day5.html"><strong aria-hidden="true">5.11.</strong> 用rust从零开发一套web框架：day5</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">6.</strong> 奇技淫巧</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../08_奇技淫巧/Copy-On-Write是不是优化？/Copy-On-Write是不是优化？.html"><strong aria-hidden="true">6.1.</strong> 译：Copy-On-Write是不是优化？</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/揭秘神奇的Rust_Axum风格的函数实现/揭秘神奇的Rust_Axum风格的函数实现.html"><strong aria-hidden="true">6.2.</strong> 译：揭秘神奇的 Rust Axum 风格的函数实现</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/“变长参数”函数与回调/“变长参数”函数与回调.html"><strong aria-hidden="true">6.3.</strong> “变长参数”函数与回调</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/Rust字符串格式化的幕后：format_args!()/Rust字符串格式化的幕后：format_args!().html"><strong aria-hidden="true">6.4.</strong> 译：Rust字符串格式化的幕后：format_args!()</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/给Rust带来一点C++特产/给Rust带来一点C++特产.html"><strong aria-hidden="true">6.5.</strong> 给Rust带来一点C++特产</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/一步步实现_Rust_Bevy_ECS_的_System_简化版本/一步步实现_Rust_Bevy_ECS_的_System_简化版本.html"><strong aria-hidden="true">6.6.</strong> 一步步实现 Rust Bevy ECS 的 System 简化版本</a></li><li class="chapter-item expanded "><a href="../../08_奇技淫巧/Exploring_Design_Patterns_in_Rust_with_Algorithmic_Trading_Examples/Exploring_Design_Patterns_in_Rust_with_Algorithmic_Trading_Examples.html"><strong aria-hidden="true">6.7.</strong> Exploring Design Patterns in Rust with Algorithmic Trading Examples</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">7.</strong> 源码分析</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../09_源码分析/Rust并发：bytes源码分析/Rust并发：bytes源码分析.html"><strong aria-hidden="true">7.1.</strong> Rust并发：bytes源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust并发：标准库Arc源码分析/Rust并发：标准库Arc源码分析.html"><strong aria-hidden="true">7.2.</strong> Rust并发：标准库Arc源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust并发：标准库sync_Once源码分析/Rust并发：标准库sync_Once源码分析.html"><strong aria-hidden="true">7.3.</strong> Rust并发：标准库sync::Once源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust源码阅读：引用计数Rc/Rust源码阅读：引用计数Rc.html"><strong aria-hidden="true">7.4.</strong> Rust源码阅读：引用计数Rc</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust源码阅读：Cell、RefCell与内部可变性/Rust源码阅读：Cell、RefCell与内部可变性.html"><strong aria-hidden="true">7.5.</strong> Rust源码阅读： Cell、RefCell与内部可变性</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/关于_Rust_的_UnsafeCell、Cell_与_RefCell/关于_Rust_的_UnsafeCell、Cell_与_RefCell.html"><strong aria-hidden="true">7.6.</strong> 关于 Rust 的 UnsafeCell、Cell 与 RefCell</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust_Async_async-stream源码分析/Rust_Async_async-stream源码分析.html"><strong aria-hidden="true">7.7.</strong> Rust Async: async-stream源码分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/走进Tokio的异步世界/走进Tokio的异步世界.html"><strong aria-hidden="true">7.8.</strong> 走进 Tokio 的异步世界</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/tokio.rs_runtime的实现/tokio.rs_runtime的实现.html"><strong aria-hidden="true">7.9.</strong> tokio.rs runtime 的实现</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Tokio_internals/Tokio_internals.html"><strong aria-hidden="true">7.10.</strong> Tokio internals</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Tokio_internals/译文：Tokio內部机制.html"><strong aria-hidden="true">7.11.</strong> 译：Tokio 内部机制</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Rust_Axum_HTTP_框架的架构分析/Rust_Axum_HTTP_框架的架构分析.html"><strong aria-hidden="true">7.12.</strong> Rust Axum HTTP 框架的架构分析</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/安利一个Rust_Game_Engine：Bevy--ECS部分/安利一个Rust_Game_Engine：Bevy--ECS部分.html"><strong aria-hidden="true">7.13.</strong> 安利一个Rust Game Engine：Bevy--ECS部分</a></li><li class="chapter-item expanded "><a href="../../09_源码分析/Tokio_解析之任务调度/Tokio_解析之任务调度.html"><strong aria-hidden="true">7.14.</strong> Tokio 解析之任务调度</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">8.</strong> 生态观察</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../11_生态观察/Rust_web_frameworks_have_subpar_error_reporting/Rust_web_frameworks_have_subpar_error_reporting.html" class="active"><strong aria-hidden="true">8.1.</strong> Rust web frameworks have subpar error reporting</a></li><li class="chapter-item expanded "><a href="../../11_生态观察/SeaORM：要做Rust版本的ActiveRecord/SeaORM：要做Rust版本的ActiveRecord.html"><strong aria-hidden="true">8.2.</strong> SeaORM：要做Rust版本的ActiveRecord</a></li></ol></li><li class="chapter-item expanded "><a href="../../empty.html"><strong aria-hidden="true">9.</strong> 死灵终极</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_总述.html"><strong aria-hidden="true">9.1.</strong> 译：Learn Rust the Dangerous Way 总述</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_0.html"><strong aria-hidden="true">9.2.</strong> 译：Learn Rust the Dangerous Way 0</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_1.html"><strong aria-hidden="true">9.3.</strong> 译：Learn Rust the Dangerous Way 1</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_2.html"><strong aria-hidden="true">9.4.</strong> 译：Learn Rust the Dangerous Way 2</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_3.html"><strong aria-hidden="true">9.5.</strong> 译：Learn Rust the Dangerous Way 3</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_4.html"><strong aria-hidden="true">9.6.</strong> 译：Learn Rust the Dangerous Way 4</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Learn_Rust_the_Dangerous_Way_系列文章翻译/Learn_Rust_the_Dangerous_Way_系列文章翻译_5.html"><strong aria-hidden="true">9.7.</strong> 译：Learn Rust the Dangerous Way 5</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测（一）.html"><strong aria-hidden="true">9.8.</strong> Unsafe Rust 随堂小测（一）</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测（二）.html"><strong aria-hidden="true">9.9.</strong> Unsafe Rust 随堂小测（二）</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测（三）.html"><strong aria-hidden="true">9.10.</strong> Unsafe Rust 随堂小测（三）</a></li><li class="chapter-item expanded "><a href="../../12_死灵终极/Unsafe_Rust_随堂小测/Unsafe_Rust_随堂小测参考答案.html"><strong aria-hidden="true">9.11.</strong> Unsafe Rust 随堂小测参考答案</a></li></ol></li></ol>
            </div>
            <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
        </nav>

        <div id="page-wrapper" class="page-wrapper">

            <div class="page">
                                <div id="menu-bar-hover-placeholder"></div>
                <div id="menu-bar" class="menu-bar sticky bordered">
                    <div class="left-buttons">
                        <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
                            <i class="fa fa-bars"></i>
                        </button>
                        <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
                            <i class="fa fa-paint-brush"></i>
                        </button>
                        <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
                            <li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
                        </ul>
                        <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
                            <i class="fa fa-search"></i>
                        </button>
                    </div>

                    <h1 class="menu-title">Rust 优秀博文</h1>

                    <div class="right-buttons">
                        <a href="../../print.html" title="Print this book" aria-label="Print this book">
                            <i id="print-button" class="fa fa-print"></i>
                        </a>

                    </div>
                </div>

                <div id="search-wrapper" class="hidden">
                    <form id="searchbar-outer" class="searchbar-outer">
                        <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
                    </form>
                    <div id="searchresults-outer" class="searchresults-outer hidden">
                        <div id="searchresults-header" class="searchresults-header"></div>
                        <ul id="searchresults">
                        </ul>
                    </div>
                </div>

                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
                <script type="text/javascript">
                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
                    });
                </script>

                <div id="content" class="content">
                    <main>
                        <h1 id="rust-web-frameworks-have-subpar-error-reporting"><a class="header" href="#rust-web-frameworks-have-subpar-error-reporting">Rust web frameworks have subpar error reporting</a></h1>
<p>作者：<a href="https://www.lpalmieri.com/">LUCA PALMIERI</a></p>
<p>原载：<a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting">https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting</a></p>
<p>None of the major Rust web frameworks have a <em>great</em> error reporting story, according to my personal definition of great.
I've been building production APIs in Rust for almost 6 years now, and I've been <a href="https://zero2prod.com/">teaching people about backend development in Rust</a> for almost as long: I've always had to tweak, work-around or actively fight the framework to get <strong>reliable</strong> and <strong>exhaustive</strong> error reporting.</p>
<p>Last year I bit the bullet and started building my own web framework, <a href="https://pavex.dev/">Pavex</a>. I channeled my frustration into a different error reporting design. This post sums up the journey and the rationale behind it.</p>
<blockquote>
<p>You can discuss this post on <a href="https://www.reddit.com/r/rust/comments/1ajm86e/rust_web_frameworks_have_subpar_error_reporting/">r/rust</a>.</p>
</blockquote>
<h2 id="table-of-contents"><a class="header" href="#table-of-contents">Table of contents</a></h2>
<ul>
<li>What are errors <em>for</em>?
<ul>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#reacting">Reacting</a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#reporting">Reporting</a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#modelling-errors-in-rust">Modelling errors in Rust</a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#the-error-trait">The <code>Error</code> trait</a></li>
</ul>
</li>
<li>Our benchmark
<ul>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#high-level-requirements">High-level requirements</a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#low-level-requirements">Low-level requirements</a></li>
</ul>
</li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#frameworks">Frameworks</a></li>
<li><code>axum</code>
<ul>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#request-handlers">Request handlers</a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#intoresponse"><code>IntoResponse</code></a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#extractors">Extractors</a></li>
<li>Can <code>axum</code> meet our requirements?
<ul>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#workaround-1">Workaround #1</a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#workaround-2">Workaround #2</a></li>
</ul>
</li>
</ul>
</li>
<li>Actix Web
<ul>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#actix-web-request-handlers">Request handlers</a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#responder"><code>Responder</code></a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#responseerror"><code>ResponseError</code></a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#httpresponse-error"><code>HttpResponse::error</code></a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#can-actix-web-meet-our-requirements">Can Actix Web meet our requirements?</a></li>
</ul>
</li>
<li>Pavex
<ul>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#error-requirements">Error requirements</a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#intoresponse"><code>IntoResponse</code></a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#error-handlers">Error handlers</a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#error-observers">Error observers</a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#can-pavex-meet-our-requirements">Can Pavex meet our requirements?</a></li>
</ul>
</li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#conclusion">Conclusion</a></li>
</ul>
<h2 id="what-are-errors-for"><a class="header" href="#what-are-errors-for">What are errors <em>for</em>?</a></h2>
<p>So many different things can go wrong in a networked application: the database is down (or slow), the caller sent invalid data, you ran out of file descriptors, etc.
Every time something goes wrong, two different concerns must be addressed: <strong>reacting</strong> and <strong>reporting</strong>.</p>
<h3 id="reacting"><a class="header" href="#reacting">Reacting</a></h3>
<p>Whoever called your API is waiting for a response!
Your application needs to <strong>convert the error into a response</strong>, using a representation that the caller can understand.</p>
<p>For an HTTP API, this involves selecting the most appropriate status code (e.g. <code>500 Internal Server Error</code> or <code>400 Bad Request</code>) and, if required, a more detailed error message in the body (e.g. an explanation of which field was invalid and why).</p>
<h3 id="reporting"><a class="header" href="#reporting">Reporting</a></h3>
<p>At the same time, as an <strong>operator</strong> (i.e. the person responsible for keeping the application up and running), you need to have <strong>a mechanism to know that an error occurred</strong>. For example, you might track the percentage of 5xx errors to page an on-call engineer if it goes above a pre-defined threshold.</p>
<p>Knowing that an error occurred is not enough though: you need to know <strong>what went wrong</strong>.
When that engineer gets paged, or when you get to work in the morning, there has to be <strong>enough information to troubleshoot the issue</strong>.</p>
<h3 id="modelling-errors-in-rust"><a class="header" href="#modelling-errors-in-rust">Modelling errors in Rust</a></h3>
<p>Rust has two ways to model failures: <a href="https://doc.rust-lang.org/std/macro.panic.html">panics</a> and <a href="https://doc.rust-lang.org/std/result/enum.Result.html"><code>Result</code></a>.
Panics are primarily used for unrecoverable errors, so I won't discuss them much here—you need to recover and send a response! Let's focus on <code>Result</code> instead.</p>
<p><a href="https://doc.rust-lang.org/std/result/enum.Result.html"><code>Result</code></a> is a type, an enum. It has two variants: success (<code>Ok</code>) or failure (<code>Err</code>). When <strong>a function can fail, it shows in its signature</strong>: it uses a <code>Result</code> as its return type.</p>
<blockquote>
<p>There's a lot to be said about good error design as a prerequisite to good error reporting, but that'd be too much of a detour. If you want to learn more about error design, check out <a href="https://www.lpalmieri.com/posts/error-handling-rust">this previous post of mine</a>—it builds on the same principles.</p>
</blockquote>
<h3 id="the-error-trait"><a class="header" href="#the-error-trait">The <code>Error</code> trait</a></h3>
<p>There are no constraints on the type of the <code>Err</code> variant, but it's a good practice to use a type that implements the <a href="https://doc.rust-lang.org/std/error/trait.Error.html"><code>std::error::Error</code></a> trait.
<a href="https://doc.rust-lang.org/std/error/trait.Error.html"><code>std::error::Error</code></a> is the cornerstone of Rust's error handling story. It requires error types to:</p>
<ul>
<li>Implement the <a href="https://doc.rust-lang.org/std/fmt/trait.Display.html"><code>Display</code></a> trait, as its <strong>user-facing representation</strong></li>
<li>Implement the <a href="https://doc.rust-lang.org/std/fmt/trait.Debug.html"><code>Debug</code></a> trait, as its <strong>operator-facing representation</strong></li>
<li>Provide a way to access the <a href="https://doc.rust-lang.org/std/error/trait.Error.html#method.source"><strong>source of the error</strong></a>, if any</li>
</ul>
<p>The last point is particularly important: error types are often <strong>wrappers around lower-level errors</strong>.
For example, a database connection error might be caused by a network error, which is in turn caused by a DNS resolution issue. When troubleshooting, you want to be able to <strong>drill down</strong> into the chain of causes. You can't fix that database connection error if your logs don't show that it was caused by a DNS resolution issue in the first place!</p>
<h2 id="our-benchmark"><a class="header" href="#our-benchmark">Our benchmark</a></h2>
<h3 id="high-level-requirements"><a class="header" href="#high-level-requirements">High-level requirements</a></h3>
<p>Let's set some expectations to properly &quot;benchmark&quot; the error reporting story of different web frameworks.
At a high level, we want the following:</p>
<ul>
<li>All errors are logged, exactly once, with enough information to troubleshoot</li>
<li>With a single log line, we can tell:
<ul>
<li>If the request failed</li>
<li>What error occurred</li>
<li>What caused the error</li>
</ul>
</li>
</ul>
<p>It should be possible to ensure that these requirements are met with <strong>minimum room for error</strong>—it shouldn't be possible to forget to log an error, or to log it in a way that's inconsistent with the rest of the application.</p>
<p>I consider this the bare minimum telemetry setup for a production-grade application. I don't expect a web framework to provide this experience out of the box (although it'd be nice!), but I do expect it to provide the necessary hooks to build it myself.</p>
<h3 id="low-level-requirements"><a class="header" href="#low-level-requirements">Low-level requirements</a></h3>
<p>We can convert this high-level specification into a set of concrete requirements:</p>
<ol>
<li>
<p>For every incoming request, there is an over-arching <a href="https://docs.rs/tracing/latest/tracing/span/index.html"><code>tracing::Span</code></a> that captures the entire request lifecycle. I'll refer to this as the <a href="https://stripe.com/blog/canonical-log-lines"><strong>root span</strong></a>.</p>
</li>
<li>
<p>Every time an error occurs, the application emits a</p>
<pre><code>tracing
</code></pre>
<p>event:</p>
<ol>
<li>Its level is set to <a href="https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.ERROR"><code>ERROR</code></a></li>
<li>The <a href="https://doc.rust-lang.org/std/fmt/trait.Display.html"><code>Display</code></a> representation of the error in recorded in the event's <code>error.msg</code> field</li>
<li>The <a href="https://doc.rust-lang.org/std/fmt/trait.Debug.html"><code>Debug</code></a> representation of the error in recorded in the event's <code>error.details</code> field</li>
<li>The chain of <a href="https://doc.rust-lang.org/std/error/trait.Error.html#method.source">sources</a> (if any) is recorded in the event's <code>error.source_chain</code> field</li>
</ol>
</li>
<li>
<p>For the error that was converted into the HTTP response returned to the caller, we capture:</p>
<ol>
<li>The <a href="https://doc.rust-lang.org/std/fmt/trait.Display.html"><code>Display</code></a> representation in the root span's <code>error.msg</code> field</li>
<li>The <a href="https://doc.rust-lang.org/std/fmt/trait.Debug.html"><code>Debug</code></a> representation in the root span's <code>error.details</code> field</li>
<li>The chain of <a href="https://doc.rust-lang.org/std/error/trait.Error.html#method.source">sources</a> in the root span's <code>error.source_chain</code> field</li>
</ol>
</li>
</ol>
<p>I've been using <a href="https://docs.rs/tracing/latest/tracing/"><code>tracing</code></a> as the structured library of choice here, but the same requirements can be expressed in terms of other logging libraries (and the framework should be able to integrate with them!).</p>
<h2 id="frameworks"><a class="header" href="#frameworks">Frameworks</a></h2>
<p>I'll start by reviewing how <a href="https://actix.rs/">Actix Web</a> and <a href="https://github.com/tokio-rs/axum"><code>axum</code></a>, the two most popular web frameworks in the Rust ecosystem, fare against these requirements<a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#rocket">1</a>. I'll then discuss <a href="https://pavex.dev/">Pavex</a>'s approach.</p>
<p>If you don't care about the details, you can skip to the <a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#conclusion">conclusion</a> to see how the frameworks compare.</p>
<h2 id="axum"><a class="header" href="#axum"><code>axum</code></a></h2>
<p>In <a href="https://github.com/tokio-rs/axum"><code>axum</code></a>, the following components can fail:</p>
<ul>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#request-handlers">Request handlers</a></li>
<li><a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#extractors">Extractors</a></li>
<li>Middlewares/arbitrary <a href="https://docs.rs/tower/"><code>tower</code></a> services</li>
</ul>
<p><code>axum</code>'s overall error handling approach is detailed in <a href="https://docs.rs/axum/0.7.4/axum/error_handling/index.html">their documentation</a>.
I'll focus on request handlers and extractors, as they're the most common error sources in applications.</p>
<h3 id="request-handlers"><a class="header" href="#request-handlers">Request handlers</a></h3>
<p>In <code>axum</code>, request handlers are asynchronous functions that return a type that implements the <a href="https://docs.rs/axum/0.7.4/axum/response/trait.IntoResponse.html"><code>IntoResponse</code></a> trait.</p>
<h3 id="intoresponse"><a class="header" href="#intoresponse"><code>IntoResponse</code></a></h3>
<p><a href="https://docs.rs/axum/0.7.4/axum/response/trait.IntoResponse.html"><code>IntoResponse</code></a> is a <strong>conversion trait</strong>: it specifies how to convert a type into an HTTP response.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub trait IntoResponse {
    fn into_response(self) -&gt; Response&lt;Body&gt;;
}
<span class="boring">}
</span></code></pre></pre>
<p><code>Result</code> implements <a href="https://docs.rs/axum/0.7.4/axum/response/trait.IntoResponse.html"><code>IntoResponse</code></a>, as long as <a href="https://docs.rs/axum/0.7.4/axum/response/trait.IntoResponse.html#impl-IntoResponse-for-Result">both the <code>Ok</code> and <code>Err</code> variants do</a>.</p>
<p>Once <code>IntoResponse::into_response</code> has been called (by the framework), the type is gone—<code>self</code> is consumed. From an error reporting perspective, this means that <strong>you can't manipulate the error anymore</strong>.</p>
<h3 id="extractors"><a class="header" href="#extractors">Extractors</a></h3>
<p><a href="https://docs.rs/axum/0.7.4/axum/extract/index.html#intro">Extractors</a> are <code>axum</code>'s dependency injection mechanism.
They're used to extract data from the request (e.g. the query string, the request body, etc.) or to reject the request if it doesn't meet certain criteria (e.g. it's missing an <code>Authorization</code> header).</p>
<p>You define an extractor by implementing either the <a href="https://docs.rs/axum/0.7.4/axum/extract/trait.FromRequest.html"><code>FromRequest</code></a> or the <a href="https://docs.rs/axum/0.7.4/axum/extract/trait.FromRequestParts.html"><code>FromRequestParts</code></a> traits.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Slightly simplified for exposition purposes
pub trait FromRequest&lt;S&gt;: Sized {
    /// If the extractor fails it'll use this &quot;rejection&quot; type. A rejection is
    /// a kind of error that can be converted into a response.
    type Rejection: IntoResponse;

    /// Perform the extraction.
    async fn from_request(req: Request, state: &amp;S) -&gt; Result&lt;Self, Self::Rejection&gt;;
}
<span class="boring">}
</span></code></pre></pre>
<p>Error handling works similarly to request handlers: if the extractor fails, it must return an error type that implements <a href="https://docs.rs/axum/0.7.4/axum/response/trait.IntoResponse.html"><code>IntoResponse</code></a>. Therefore, it suffers from the same limitations.</p>
<h3 id="can-axum-meet-our-requirements"><a class="header" href="#can-axum-meet-our-requirements">Can <code>axum</code> meet our requirements?</a></h3>
<p><code>axum</code> provides no mechanism to execute logic between the request handler returning an error, and that very same error being converted into an HTTP response via <a href="https://docs.rs/axum/0.7.4/axum/response/trait.IntoResponse.html"><code>IntoResponse::into_response</code></a>. The same is true for extractors.
If you want to log errors, you must do it:</p>
<ul>
<li>In your request handler/extractor</li>
<li>Inside the <code>IntoResponse</code> implementation</li>
</ul>
<p>Neither is ideal.</p>
<p>You don't have a <strong>single place</strong> where the logging logic lives<a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#trace-layer">2</a>. You end up with log statements spread out across the entire codebase. It's easy for an error to slip through the cracks, unlogged, or for logging logic to evolve inconsistently over time.
Things get worse if you use error types defined in other crates—you can't add logging to their <code>IntoResponse</code> implementation, nor customize it if it's there. Perhaps they are emitting a <code>tracing</code> error event, but they aren't using the same field names or they aren't recording the source chain.</p>
<p>Out of the box, <code>axum</code> comes quite short of meeting <a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#low-level-requirements">the telemetry requirements</a> I laid down. You can try to implement some mitigation strategies, described below, but neither is bullet-proof.</p>
<h3 id="workaround-1"><a class="header" href="#workaround-1">Workaround #1</a></h3>
<p>You can try to wrap<a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#single-error-type">3</a> all your errors with a single custom error type (e.g. <code>ErrorLogger&lt;E&gt;</code>). You then implement <a href="https://docs.rs/axum/0.7.4/axum/response/trait.IntoResponse.html"><code>IntoResponse</code></a> for the wrapper and add the logging logic there.
This still isn't a bulletproof solution:</p>
<ul>
<li>You may forget to wrap one of your errors with the custom error wrapper.</li>
<li>You can no longer use extractors defined in other crates (including <code>axum</code> itself!). You need to wrap <em>all third-party extractors</em> to ensure they return a wrapped error.</li>
</ul>
<p>This workaround, even if applied correctly, would still fail to meet <a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#low-level-requirements">all our requirements</a>: from inside <a href="https://docs.rs/axum/0.7.4/axum/response/trait.IntoResponse.html"><code>IntoResponse</code></a> you can't access extractors, therefore you have no way to reliably access the root span for the current request and attach error details to it.</p>
<h3 id="workaround-2"><a class="header" href="#workaround-2">Workaround #2</a></h3>
<blockquote>
<p>Later edit: this approach was suggested in the <a href="https://www.reddit.com/r/rust/comments/1ajm86e/rust_web_frameworks_have_subpar_error_reporting/">r/rust comment section</a>.</p>
</blockquote>
<p>The approach above can be refined using <a href="https://docs.rs/axum/latest/axum/response/type.Response.html#method.extensions"><code>Response</code>'s extensions</a>.
You still need to wrap all errors with a custom wrapper, but you don't eagerly log the error inside <a href="https://docs.rs/axum/0.7.4/axum/response/trait.IntoResponse.html"><code>IntoResponse</code></a>. You instead store<a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#store">4</a> the error in the extensions attached to the <code>Response</code>. A logging middleware then tries to extract the error type from the extensions to log it.</p>
<p>The middleware can access the root span, coming closer to meeting our requirements.
The underlying challenges remain unresolved: there is no reliable way to ensure you wrapped <em>all</em> errors and you need to wrap all third-party extractors, including those defined in <code>axum</code> itself.</p>
<h2 id="actix-web"><a class="header" href="#actix-web">Actix Web</a></h2>
<p>In <a href="https://actix.rs/">Actix Web</a>, the following components can fail:</p>
<ul>
<li>Request handlers</li>
<li>Extractors</li>
<li>Middlewares/arbitrary <a href="https://docs.rs/actix-web/4.5.1/actix_web/dev/trait.Service.html">Service</a> implementations</li>
</ul>
<p>Actix Web's overall error handling approach is detailed <a href="https://actix.rs/docs/errors">on their website</a>.
Just like with <code>axum</code>, I'll focus on request handlers and extractors, as they're the most common error sources in applications.</p>
<h3 id="request-handlers-1"><a class="header" href="#request-handlers-1">Request handlers</a></h3>
<p>In Actix Web, request handlers are asynchronous functions that return a type that implements the <a href="https://docs.rs/actix-web/4.5.1/actix_web/trait.Responder.html"><code>Responder</code></a> trait.</p>
<h3 id="responder"><a class="header" href="#responder"><code>Responder</code></a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub trait Responder {
    type Body: MessageBody + 'static;

    // Required method
    fn respond_to(self, req: &amp;HttpRequest) -&gt; HttpResponse&lt;Self::Body&gt;;
}
<span class="boring">}
</span></code></pre></pre>
<p><a href="https://docs.rs/actix-web/4.5.1/actix_web/trait.Responder.html"><code>Responder</code></a> is a <strong>conversion trait</strong>: it specifies how to convert a type into an HTTP response. Just like <a href="https://docs.rs/axum/0.7.4/axum/response/trait.IntoResponse.html"><code>axum</code>'s <code>IntoResponse</code></a>, once <code>Responder::respond_to</code> has been called (by the framework), the type is gone—<code>self</code> is consumed.</p>
<p><code>Result</code> implements <a href="https://docs.rs/actix-web/4.5.1/actix_web/trait.Responder.html"><code>Responder</code></a>, as long as:</p>
<ul>
<li>the <code>Ok</code> variant implements <a href="https://docs.rs/actix-web/4.5.1/actix_web/trait.Responder.html"><code>Responder</code></a></li>
<li>the <code>Err</code> variant implements the <a href="https://docs.rs/actix-web/4.5.1/actix_web/trait.ResponseError.html"><code>ResponseError</code></a> trait</li>
</ul>
<h3 id="responseerror"><a class="header" href="#responseerror"><code>ResponseError</code></a></h3>
<p><a href="https://docs.rs/actix-web/4.5.1/actix_web/trait.ResponseError.html"><code>ResponseError</code></a> is another <strong>conversion trait</strong>, specialised for errors—it provides a cheap way to check the status code of the resulting response without having to build it wholesale.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub trait ResponseError: Debug + Display {
    fn status_code(&amp;self) -&gt; StatusCode;
    fn error_response(&amp;self) -&gt; HttpResponse&lt;BoxBody&gt;;
}
<span class="boring">}
</span></code></pre></pre>
<p>Notice one key detail: neither <code>status_code</code> nor <code>error_response</code> consume <code>self</code>. They both <strong>take a reference to the error type as input</strong>. You might be thinking: &quot;It doesn't matter, <code>Responder::respond_to</code> consumes <code>self</code> anyway, so we can't log the error anymore!&quot;
But here comes the twist: <a href="https://docs.rs/actix-web/4.5.1/actix_web/struct.HttpResponse.html#method.error"><code>HttpResponse::error</code></a></p>
<h3 id="httpresponseerror"><a class="header" href="#httpresponseerror"><code>HttpResponse::error</code></a></h3>
<p>In Actix Web, when an <a href="https://docs.rs/actix-web/4.5.1/actix_web/struct.HttpResponse.html"><code>HttpResponse</code></a> is built from an error (via <a href="https://docs.rs/actix-web/4.5.1/actix_web/struct.HttpResponse.html#method.from_error"><code>HttpResponse::from_error</code></a>), the <strong>error is stored as part of the response</strong>. You can still access the error after the response has been built!</p>
<h3 id="extractors-1"><a class="header" href="#extractors-1">Extractors</a></h3>
<p>In Actix Web, extractors are types that implement the <a href="https://docs.rs/actix-web/4.5.1/actix_web/trait.FromRequest.html"><code>FromRequest</code></a>. In terms of error handling, they work similarly to request handlers: if the extractor fails, it must return an error type that can be converted into <a href="https://docs.rs/actix-web/4.5.1/actix_web/error/struct.Error.html"><code>actix_web::Error</code></a> which is in turn converted into <a href="https://docs.rs/actix-web/4.5.1/actix_web/struct.HttpResponse.html"><code>HttpResponse</code></a> via its <a href="https://docs.rs/actix-web/4.5.1/actix_web/trait.ResponseError.html"><code>ResponseError</code></a> implementation.</p>
<h3 id="can-actix-web-meet-our-requirements"><a class="header" href="#can-actix-web-meet-our-requirements">Can Actix Web meet our requirements?</a></h3>
<p>Almost.
You can write an Actix Web middleware that checks if the current response bundles an error and, if so, log it.
That's exactly what I did in <a href="https://github.com/LukeMathWalker/tracing-actix-web"><code>tracing-actix-web</code></a>.</p>
<p><code>tracing-actix-web</code> was indeed built to meet <a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#low-level-requirements">the requirements</a> I set at the beginning of this post, but <strong>it falls short</strong>: <strong>only the last error is going to be logged</strong>.</p>
<p>You can see why that's the case by following this scenario:</p>
<ul>
<li>A request handler returns an error</li>
<li>The error is converted into an HTTP response and stored in the response</li>
<li>The response passes through an unrelated middleware, which fails<a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#actix-middleware">5</a> and builds a new response from the new error</li>
<li>The logging middleware sees the final response and logs the last error</li>
</ul>
<p>The logging middleware never gets a chance to see the first error since the corresponding response has been thrown away. This is unfortunately a fundamental limitation of Actix Web's current error handling design.</p>
<h2 id="pavex"><a class="header" href="#pavex">Pavex</a></h2>
<p><a href="https://pavex.dev/">Pavex</a> is a new web framework I'm building. It's currently going through a private beta, but you can find the documentation <a href="https://pavex.dev/docs/">here</a>.</p>
<p>In <a href="https://pavex.dev/">Pavex</a>, the following components can fail:</p>
<ul>
<li><a href="https://pavex.dev/docs/guide/routing/request_handlers/">Request handlers</a></li>
<li><a href="https://pavex.dev/docs/guide/dependency_injection/">Constructors</a> (i.e. our equivalent of extractors)</li>
<li><a href="https://pavex.dev/docs/guide/middleware/">Middlewares</a></li>
</ul>
<p>You can find a detailed overview of the error handling story in the <a href="https://pavex.dev/docs/guide/errors/">documentation</a>.</p>
<h3 id="error-requirements"><a class="header" href="#error-requirements">Error requirements</a></h3>
<p>There is only one requirement for errors in Pavex: it must be possible to convert them into a <a href="https://pavex.dev/docs/api_reference/pavex/struct.error"><code>pavex::Error</code></a> via <a href="https://pavex.dev/docs/api_reference/pavex/struct.error#method.new"><code>pavex::Error::new</code></a>.
All errors that implement the <a href="https://doc.rust-lang.org/std/error/trait.Error.html"><code>std::error::Error</code></a> trait can be converted into a <a href="https://pavex.dev/docs/api_reference/pavex/struct.error"><code>pavex::Error</code></a>, as well as some other types that can't implement it directly—e.g. <a href="https://docs.rs/anyhow/1.0.79/anyhow/struct.Error.html"><code>anyhow::Error</code></a> or <a href="https://docs.rs/eyre/0.6.12/eyre/struct.Report.html"><code>eyre::Report</code></a>.</p>
<h3 id="intoresponse-1"><a class="header" href="#intoresponse-1"><code>IntoResponse</code></a></h3>
<p>Pavex, just like Actix Web and <code>axum</code>, has a <strong>conversion trait</strong> that specifies how to convert a type into an HTTP response: <a href="https://pavex.dev/docs/api_reference/pavex/response/trait.IntoResponse.html"><code>IntoResponse</code></a>.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub trait IntoResponse {
    // Required method
    fn into_response(self) -&gt; Response;
}
<span class="boring">}
</span></code></pre></pre>
<p>There's a key difference though: <strong><a href="https://pavex.dev/docs/api_reference/pavex/response/trait.IntoResponse.html"><code>IntoResponse</code></a> is not implemented for <code>Result</code></strong>.</p>
<h3 id="error-handlers"><a class="header" href="#error-handlers">Error handlers</a></h3>
<p>To convert an error into an HTTP response, you must register <strong>an error handler</strong>.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use pavex::blueprint::router::POST;
use pavex::blueprint::Blueprint;
use pavex::f;

pub fn blueprint() -&gt; Blueprint {
    let mut bp = Blueprint::new();
    // The `handler` for the `/login` route returns a `Result`
    bp.route(POST, &quot;/login&quot;, f!(crate::core::handler))
        // We specify which function should be called to
        // convert the error into an HTTP response
        .error_handler(f!(crate::core::login_error2response));
    // [...]
}
<span class="boring">}
</span></code></pre></pre>
<p>An error handler is a function or method that takes a <strong>reference to the error type</strong> and returns a type that implements <a href="https://pavex.dev/docs/api_reference/pavex/response/trait.IntoResponse.html"><code>IntoResponse</code></a>.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use pavex::http::StatusCode;

pub async fn login_error2response(e: &amp;LoginError) -&gt; StatusCode  {
    match e {
        LoginError::InvalidCredentials =&gt; StatusCode::UNAUTHORIZED,
        LoginError::DatabaseError =&gt; StatusCode::INTERNAL_SERVER_ERROR,
    }
}
<span class="boring">}
</span></code></pre></pre>
<h3 id="error-observers"><a class="header" href="#error-observers">Error observers</a></h3>
<p>After Pavex has generated an HTTP response from the error, using the error handler you registered, it converts your concrete error type into a <a href="https://pavex.dev/docs/api_reference/pavex/struct.error"><code>pavex::Error</code></a> and invokes your <strong>error observers</strong>.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub async fn log_error(e: &amp;pavex::Error) {
    tracing::error!(&quot;An error occurred: {}&quot;, e);
}
<span class="boring">}
</span></code></pre></pre>
<p>An error observer is a function or method that takes a <strong>reference to <a href="https://pavex.dev/docs/api_reference/pavex/struct.error"><code>pavex::Error</code></a></strong> as input and returns nothing.
They are designed for error reporting—e.g. you can use them to log errors, increment a metric counter, etc.</p>
<p>You can register as many error observers as you want, and they will all be invoked in the order they were registered:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use pavex::blueprint::router::POST;
use pavex::blueprint::Blueprint;
use pavex::f;

pub fn blueprint() -&gt; Blueprint {
    let mut bp = Blueprint::new();
    bp.error_observer(f!(crate::core::log_error));
    // [...]
}
<span class="boring">}
</span></code></pre></pre>
<h3 id="can-pavex-meet-our-requirements"><a class="header" href="#can-pavex-meet-our-requirements">Can Pavex meet our requirements?</a></h3>
<p>Yes!
<a href="https://pavex.dev/">Pavex</a> invokes error observers for every error that occurs—by construction, you simply can't forget an error along the way.
Error observers can take advantage of dependency injection, therefore they access the root span for the current request and attach error details to it. That's exactly what happens in the starter project generated by <code>pavex new</code>, using the following error observer:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub async fn log_error(e: &amp;pavex::Error, root_span: &amp;RootSpan) {
    let source_chain = error_source_chain(e);
    // Emit an error event
    tracing::error!(
        error.msg = %e,
        error.details = ?e,
        error.source_chain = %source_chain,
        &quot;An error occurred during request handling&quot;,
    );
    // Attach the error details to the root span
    // If multiple errors occur, the details of the last one will &quot;prevail&quot;
    root_span.record(&quot;error.msg&quot;, tracing::field::display(e));
    root_span.record(&quot;error.details&quot;, tracing::field::debug(e));
    root_span.record(&quot;error.source_chain&quot;, error_source_chain(e));
}
<span class="boring">}
</span></code></pre></pre>
<p>That's all you need to meet <a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#low-level-requirements">the requirements</a> I set at the beginning of this post.
No workarounds, no sharp edges, no corner cases.</p>
<h2 id="conclusion"><a class="header" href="#conclusion">Conclusion</a></h2>
<p>It is not possible to fully and reliably satisfy <a href="https://www.lpalmieri.com/posts/rust-web-frameworks-have-subpar-error-reporting/#low-level-requirements">our telemetry requirements</a> with either <code>axum</code> nor Actix Web.
Actix Web comes much closer though: that's why I still recommend Actix Web over <code>axum</code> when people ask me for advice on which Rust web framework to use for their next project. Solid error reporting is <strong>that</strong> important to me.</p>
<p><a href="https://pavex.dev/">Pavex</a>, on the other hand, easily meets all the requirements.
It's not a coincidence: I've been building it with these requirements in mind from day one, making error reporting a first-class concern. I'm confident to say that, right now, <strong><a href="https://pavex.dev/">Pavex</a> has the best error reporting story in the Rust web ecosystem</strong>.</p>
<p>Nonetheless, there is no intrinsic limitation preventing Actix Web or <code>axum</code> from converging to a similar design (or perhaps a new one!) to resolve the issues I've highlighted in this post.
I sincerely hope that happens—the main advantage of having different frameworks is the constant cross-pollination of ideas and the pressure to improve.</p>
<hr />
<blockquote>
<p>You can discuss this post on <a href="https://www.reddit.com/r/rust/comments/1ajm86e/rust_web_frameworks_have_subpar_error_reporting/">r/rust</a>.</p>
</blockquote>
<blockquote>
<p><a href="https://www.lpalmieri.com/subscribe">Subscribe to the newsletter</a> if you want to be notified when a post is published!
You can also follow the development of <a href="https://pavex.dev/">Pavex</a> on <a href="https://github.com/LukeMathWalker/pavex">GitHub</a>.</p>
</blockquote>
<hr />
<h2 id="footnotes"><a class="header" href="#footnotes">Footnotes</a></h2>
<p>1 I originally wanted to include <a href="https://rocket.rs/">Rocket</a> in this comparison, but I quickly realised that it doesn't provide enough hooks to even wrap a <code>tracing::Span</code> around the request-handling <code>Future</code>. That's a prerequisite to a correct implementation of structured logging, there's no point in going further without it.</p>
<p>2 If you're using <code>TraceLayer</code>, from <code>tower_http</code>, you might be wondering: isn't that enough? Isn't <em>that</em> the single place? Unfortunately, <code>TraceLayer::on_failure</code> doesn't get to see the error, it only looks at the response generated by the error!</p>
<p>3 There's another variation of this approach: you return the same error type (e.g. <code>ApiError</code>) from all your extractors, request handlers and middlewares. The two approaches are fundamentally equivalent.</p>
<p>4 What's stored inside <a href="https://docs.rs/http/1.0.0/http/struct.Extensions.html#method.insert"><code>Extensions</code></a> has to be clonable. This can be solved by wrapping the original error inside an <code>Arc</code>.</p>
<p>5 There's another issue with failures in Actix Web middlewares, but it'd take forever to get into the details and explain it. The TL;DR is that the invocation of the downstream portion of the middleware stack should return a <code>Response</code> but it now returns a <code>Result</code>, creating a weird separate track for errors that's hard to integrate with the overall error handling story.</p>

                    </main>

                    <nav class="nav-wrapper" aria-label="Page navigation">
                        <!-- Mobile navigation buttons -->
                            <a rel="prev" href="../../empty.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                                <i class="fa fa-angle-left"></i>
                            </a>

                            <a rel="next" href="../../11_生态观察/SeaORM：要做Rust版本的ActiveRecord/SeaORM：要做Rust版本的ActiveRecord.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                                <i class="fa fa-angle-right"></i>
                            </a>

                        <div style="clear: both"></div>
                    </nav>
                </div>
            </div>

            <nav class="nav-wide-wrapper" aria-label="Page navigation">
                    <a rel="prev" href="../../empty.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                        <i class="fa fa-angle-left"></i>
                    </a>

                    <a rel="next" href="../../11_生态观察/SeaORM：要做Rust版本的ActiveRecord/SeaORM：要做Rust版本的ActiveRecord.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
                        <i class="fa fa-angle-right"></i>
                    </a>
            </nav>

        </div>




        <script type="text/javascript">
            window.playground_copyable = true;
        </script>


        <script src="../../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../mark.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../searcher.js" type="text/javascript" charset="utf-8"></script>

        <script src="../../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../highlight.js" type="text/javascript" charset="utf-8"></script>
        <script src="../../book.js" type="text/javascript" charset="utf-8"></script>

        <!-- Custom JS scripts -->


    </body>
</html>
